//
// Created by Jinyu Zhu on 2022/4/19.
//

#include "graphd.h"
#include "sstream"
#include "algorithm"

int DirectedGraph::AddNodeReplica(Node* NodeP) {
    if (IsNode(NodeP->Id)) return -1;

    MaxNId = std::max(MaxNId, (NodeP->Id)+1);
    NodeMap.insert({NodeP->Id, Node(NodeP->Id, NodeP->Name, NodeP->TimeStamp, NodeP->Value)});
    if (NodeIndex.find(NodeP->Name) == NodeIndex.end()) NodeIndex.insert({NodeP->Name, std::vector<int>()});
    sorted_insert(NodeIndex.at(NodeP->Name), NodeP->Id);

    return NodeP->Id;
}


int DirectedGraph::AddEdgeReplica(DirectedGraph::Edge *EdgeP) {
    if (!(IsNode(EdgeP->SrcNId) && IsNode(EdgeP->DstNId))) return -1;

    MaxEId = std::max(MaxEId, (EdgeP->Id)+1);
    EdgeMap.insert({EdgeP->Id, Edge(EdgeP->Id, EdgeP->SrcNId, EdgeP->DstNId, EdgeP->RelationType, EdgeP->TimeStamp, EdgeP->TsvId)});
    GetNode(EdgeP->SrcNId).InsOutEIdVSorted(EdgeP->Id);
    GetNode(EdgeP->DstNId).InsInEIdVSorted(EdgeP->Id);
    return EdgeP->Id;
}

void DirectedGraph::Node::Save(FOut &fOut) const {
    fOut.Save(Id); fOut.Save(Name); fOut.Save(TimeStamp); fOut.Save(Value);
    fOut.Save(InEIdV); fOut.Save(OutEIdV);
}

DirectedGraph::Node::Node(FIn &fIn): Id(-1), Name(), TimeStamp(0), Value(0), InEIdV(), OutEIdV() {
    fIn.Load(Id); Name = fIn.LoadStr(); fIn.Load(TimeStamp); fIn.Load(Value);
    fIn.Load(InEIdV); fIn.Load(OutEIdV);
}

void DirectedGraph::Edge::Save(FOut &fOut) const {
    fOut.Save(Id); fOut.Save(SrcNId); fOut.Save(DstNId);
    fOut.Save(RelationType); fOut.Save(TsvId); fOut.Save(TimeStamp);
}

DirectedGraph::Edge::Edge(FIn &fIn): Id(-1), SrcNId(-1), DstNId(-1), RelationType(), TsvId(), TimeStamp(0) {
    fIn.Load(Id); fIn.Load(SrcNId); fIn.Load(DstNId);
    RelationType = fIn.LoadStr(); TsvId = fIn.LoadStr(); fIn.Load(TimeStamp);
}

bool DirectedGraph::IsEdge(const int &SrcNId, const int &DstNId) {
    if (!(IsNode(SrcNId) && IsNode(DstNId))) return false;
    Node& SrcNode = GetNode(SrcNId);
    Node& DstNode = GetNode(DstNId);

    for (int edge = 0; edge < SrcNode.GetOutDeg(); edge++) {
        const Edge& E = GetEdge(SrcNode.GetOutEId(edge));
        if (DstNId == E.GetDstNId()) {
            return true;
        }
    }
    return false;
}

int DirectedGraph::AddNode(const std::string &Name, const long& TimeStamp_, const double& Value_) {
    int NewNId = MaxNId; MaxNId++;
    NodeMap.insert({NewNId, Node(NewNId, Name, TimeStamp_, Value_)});

    if (NodeIndex.find(Name) == NodeIndex.end()) NodeIndex.insert({Name, std::vector<int>()});
    sorted_insert(NodeIndex.at(Name), NewNId);
    return NewNId;
}

int DirectedGraph::AddEdge(const int &SrcNId, const int &DstNId, const std::string &RelationType,
                           const long &TimeStamp_, const std::string &TsvId_) {
    if (!(IsNode(SrcNId) && IsNode(DstNId))) return -1;
    int NewEId = MaxEId; MaxEId++;

    EdgeMap.insert({NewEId, Edge(NewEId, SrcNId, DstNId, RelationType, TimeStamp_, TsvId_)});
    GetNode(SrcNId).InsOutEIdVSorted(NewEId);
    GetNode(DstNId).InsInEIdVSorted(NewEId);

    return NewEId;
}

int DirectedGraph::DelNode(const int &NId) {
    if (!IsNode(NId)) return -1;
    {
        Node& N = GetNode(NId);
        for (int out = 0; out < N.GetOutDeg(); out++) {
            const int EId = N.GetOutEId(out);
            const Edge& E = GetEdge(EId);
            AssertR(E.GetSrcNId() == NId, "incomplete node/edge to delete");
            binary_delete_if_exists(GetNode(E.DstNId).InEIdV, EId);
            EdgeMap.erase(EId);
        }
        for (int in = 0; in < N.GetInDeg(); in++) {
            const int EId = N.GetInEId(in);
            const Edge& E = GetEdge(EId);
            AssertR(E.GetDstNId() == NId, "incomplete node/edge to delete");
            binary_delete_if_exists(GetNode(E.SrcNId).OutEIdV, EId);
            EdgeMap.erase(EId);
        }
        binary_delete_if_exists(NodeIndex.at(N.Name), NId);
        if (NodeIndex.at(N.Name).empty()) {
            NodeIndex.erase(N.Name);
        }
    }
    NodeMap.erase(NId);
    return NId;
}

int DirectedGraph::DelEdge(const int &EId) {
    if (!IsEdge(EId)) return -1;
    {
        Edge& E = GetEdge(EId);
        binary_delete_if_exists(GetNode(E.SrcNId).OutEIdV, EId);
        binary_delete_if_exists(GetNode(E.DstNId).InEIdV, EId);
    }
    EdgeMap.erase(EId);
    return EId;
}

std::string DirectedGraph::BriefInfo() {
    std::stringstream ss;
    ss << "multi-directed graph: ";
    ss << "\n nodes: " << std::to_string(NodeMap.size()) << " edges: " << std::to_string(EdgeMap.size());
    return ss.str();
}

DGNodeI DirectedGraph::BeginNodeI() {
    DGNodeI BeginNodeI = DGNodeI(NodeMap.begin(), this);
    return { BeginNodeI };
}

DGNodeI DirectedGraph::EndNodeI() {
    DGNodeI EndNodeI = DGNodeI(NodeMap.end(), this);
    return { EndNodeI };
}

DGNodeI DirectedGraph::GetNodeNIdI(const int &NId) {
    DGNodeI NodeI = DGNodeI(NodeMap.find(NId), this);
    return { NodeI };
}

DGEdgeI DirectedGraph::BeginEdgeI() {
    DGEdgeI BeginEdgeI = DGEdgeI(EdgeMap.begin(), this);
    return { BeginEdgeI };
}

DGEdgeI DirectedGraph::EndEdgeI() {
    DGEdgeI EndEdgeI = DGEdgeI(EdgeMap.end(), this);
    return { EndEdgeI };
}

DGEdgeI DirectedGraph::GetEdgeNIdI(const int& EId) {
    DGEdgeI EdgeI = DGEdgeI(EdgeMap.find(EId), this);
    return { EdgeI };
}